## -*- coding: UTF-8 -*-
"""
Module representing "Edit book description" dialog
"""

import pygtk
import gtk
import gobject

from .. import exceptions
from .. import path
from .. import collection
from .. import settings
from .. import log
from .. import strunicode as su
from .. import core

parent = None

def initialize(parent_window):
    """
    Initialize module. parent has to be a top-level window of the application.
    """
    global parent

    if not parent is None:
        raise exceptions.InitializationError("Module already initialized!")

    parent = parent_window

items_list = dict()

def create_common_item_value_widget(**kwargs):
    """
    Create item widgets, this function serves items without additional parameters
    
    @return: tuple (item widget that serves item input, main input widget)
    """
    text_entry = gtk.Entry()
    text_entry.set_text(kwargs['initial_value'])
    return text_entry, text_entry

def create_int_param_item_value_widget(**kwargs):
    """
    Create widget that displays item with optional integer value
    """
    box = gtk.HBox()
    text_entry = gtk.Entry()
    text_entry.set_data("type", "attr_value")
    text_entry.set_text(kwargs['initial_value'])
    box.pack_start(text_entry)
    
    spin_button = gtk.SpinButton(climb_rate=10)
    spin_button.set_tooltip_text(kwargs['tooltip'])
    spin_button.set_data("param_name", kwargs['param_name'])
    if 'max_range' in kwargs:
        max_range = kwargs['max_range']
    else:
        max_range = 100.0
    spin_button.set_range(1, max_range)
    spin_button.set_value(kwargs['param_value'])
    
    box.pack_end(spin_button, expand=False, fill=True)
    box.show_all()
    
    return box, text_entry

def create_genre_item_value_widget(**kwargs):
    if not 'param_name' in kwargs:
        kwargs['param_name'] = "match"
        
    if not 'param_value' in kwargs:
        kwargs['param_value'] = 100.0
    else:
        try:
            kwargs['param_value'] = float(kwargs['param_value'])
        except:
            kwargs['param_value'] = 100.0
    
    kwargs['max_range'] = 100.0;
    kwargs['tooltip'] = _("Percentage value between 1 and 100, shows how much genre matches the book.")
         
    return create_int_param_item_value_widget(**kwargs)

def create_sequence_item_value_widget(**kwargs):
    if not 'param_name' in kwargs:
        kwargs['param_name'] = "number"
        
    if not 'param_value' in kwargs:
        kwargs['param_value'] = 0.0
    else:
        try:
            kwargs['param_value'] = float(kwargs['param_value'])
        except:
            kwargs['param_value'] = 0.0
    
    kwargs['tooltip'] = _("Book position in the given sequence.")
         
    return create_int_param_item_value_widget(**kwargs)
    
def process_common_item_value_widget(item_value_widget, **kwargs):
    """
    Process common item value widget and extract metadata from there
    
    @return: metadata item or None in case of error
    """
    # item_value_widget must be gtk.Entry instance
    if not isinstance(item_value_widget, gtk.Entry):
        log.error("process_common_item_value_widget() failed, provided object is not a gtk.Entry instance")
        return None
    
    item_id = item_value_widget.get_data("item_id")
    key, label, item_value_widget, fill_button = items_list[item_id]
    
    return {'attr_name': key, 'attr_value': su.ustr2unicode(item_value_widget.get_text()), 'attr_params': []}
    
def process_int_param_item_value_widget(item_value_widget, **kwargs):
    """
    Process widget that displays item with optional integer value and extract metadata from there
    """
    if not isinstance(item_value_widget, gtk.HBox):
        log.error("process_int_param_item_value_widget() failed, provided object is not a gtk.HBox instance")
        return None
    
    item_id = item_value_widget.get_data("item_id")
    key, label, item_value_widget, fill_button = items_list[item_id]
    
    # find widget that contain string value and widget that contains int valus
    attr_value_widget = None
    param_value_widget = None
    for c in item_value_widget.get_children():
        if c.get_data("type") == "attr_value":
            attr_value_widget = c
            break
        
    for c in item_value_widget.get_children():
        if not c.get_data("param_name") is None:
            param_value_widget = c
            break
        
    if attr_value_widget is None or param_value_widget is None:
        log.error("process_int_param_item_value_widget() failed, unable to find all required widgets")
        return None

    params = [
        [param_value_widget.get_data("param_name"), int(param_value_widget.get_value())]
        ]
    return {'attr_name': key, 'attr_value': su.ustr2unicode(attr_value_widget.get_text()), 'attr_params': params}

"""
Function for creating input widget for given item types.

(create_item_value_widget_func, process_item_value_widget_func)
"""
item_value_widget_funcs = {
    u"title": (create_common_item_value_widget, process_common_item_value_widget),
    u"author": (create_common_item_value_widget, process_common_item_value_widget),
    u"genre": (create_genre_item_value_widget, process_int_param_item_value_widget),
    u"sequence": (create_sequence_item_value_widget, process_int_param_item_value_widget)
    }

"""
Localized item titles
"""
titles_map = {
    u"title": _("Title"),
    u"author": _("Author"),
    u"genre": _("Genre"),
    u"sequence": _("Series")
    }

def run_dialog(file_id):
    """
    Display book metadata editing dialog
    
    @param file_id: 
    @return: True if data were changed, False otherwise
    """
    global items_list
    
    if parent is None:
        raise exceptions.InitializationError("Module must be initialized before first use!")
    
    xml = gtk.glade.XML(path.get_data_path("pybookshelf.glade"), root="edit_metadata_dialog", 
        domain="pybookshelf")
    xml_item = gtk.glade.XML(path.get_data_path("pybookshelf.glade"), root="edit_metadata_item_dialog",
        domain="pybookshelf")
    
    # main edit metadata dialog window
    dialog = xml.get_widget("edit_metadata_dialog")
    
    # dialog window for editing single item value
    edit_item_dialog = xml_item.get_widget("edit_metadata_item_dialog")
    
    
    # list of search results inside "edit_item_dialog"
    results_treeview = xml_item.get_widget("results_treeview")
    results_treeview.connect("cursor-changed", on_edit_item_treeview_click, xml_item.get_widget("text_entry"))
    results_treeview.connect("row-activated", on_edit_item_treeview_activated, edit_item_dialog)
    xml_item.get_widget("text_entry").connect("activate", on_edit_item_entry_activated, results_treeview)

    edit_item_dialog.set_data("treeview", results_treeview)
    edit_item_dialog.set_data("entry", xml_item.get_widget("text_entry"))
    
    # initialize search results treeview
    results_treeview_column = gtk.TreeViewColumn(_("Search results"))
    results_treeview.append_column(results_treeview_column)
    tv_column_cell = gtk.CellRendererText()
    results_treeview_column.pack_start(tv_column_cell, True)
    results_treeview_column.add_attribute(tv_column_cell, "text", 0)
    
    dialog.set_data("edit_item_dialog", edit_item_dialog)
    dialog.set_transient_for(parent)
    
    book_title = collection.get_book_title(file_id)
    dialog.set_title(_(u"Edit book “%s” metadata") % book_title)
    
    new_item_menu = gtk.Menu()
    xml.get_widget("new_item_button").connect("pressed", on_new_item_menu, dialog, new_item_menu)
    
    # fill dialog with data input fields
    items_table = xml.get_widget("items_table")
    
    metadata = collection.get_book_metadata(file_id)
    # list of item types those should be displayed first
    preferred_items = [u"author", u"title", u"genre"]
    items = dict()
    
    for m in metadata:
        if m['attr_name'] in preferred_items:
            key = m['attr_name']
        else:
            key = u"other"
          
        if not key in items:
            items[key] = list()  
        
        items[key].append(m)
        
    # fill the new item popup menu
    for key in titles_map:
        mi = gtk.MenuItem(titles_map[key])
        mi.connect("activate", on_new_item, items_table, key, dialog)
        new_item_menu.append(mi)
    new_item_menu.show_all()

    #items_table.resize(len(metadata), 4)
    for key in preferred_items + [u"other"]:
        if not key in items:
            # display only known metadata items
            continue
        
        for item in items[key]:
            if not len(item['attr_params']):
                append_item(dialog, items_table, item['attr_name'], initial_value=item['attr_value'])
                continue
            
            for param_name, param_value in item['attr_params']:
                append_item(dialog, items_table, item['attr_name'], initial_value=item['attr_value'], 
                    param_name=param_name, param_value=param_value)

    items_table.show_all()
        
    # restore window size and position
    settings.restore_window_geometry(dialog, "edit_metadata")
    settings.restore_window_geometry(edit_item_dialog, "edit_metadata_item")
    
    dialog.connect("configure_event", on_resize, "edit_metadata")
    edit_item_dialog.connect("configure_event", on_resize, "edit_metadata_item")
    
    # execute dialog
    response = dialog.run()
    dialog.hide_all()
    
    if response == 1:
        log.debug("Saving metadata")
        # delete all metadata items that correspond to items we can handle
        new_metadata = []
        for metadata_item in metadata:
            if not metadata_item['attr_name'] in item_value_widget_funcs:
                new_metadata.append(metadata_item)
                
        for item_id in items_list:
            key, label, item_value_widget, fill_button = items_list[item_id]
            metadata_item = process_item(key, item_value_widget)
            # try to find metadata item with the same attr_name and attr_value
            found = False
            for i in xrange(len(new_metadata)):
                if new_metadata[i]['attr_value'] == metadata_item['attr_value'] \
                        and new_metadata[i]['attr_name'] == metadata_item['attr_name']:
                    new_metadata[i]['attr_params'] += metadata_item['attr_params']
                    found = True
            
            if not found:
                new_metadata.append(metadata_item)
            
        collection.set_book_metadata(file_id, new_metadata)

    dialog.destroy()
    edit_item_dialog.destroy()
    items_list = dict()
    
    return response == 1

def append_item(dialog, items_table, key, initial_value, **kwargs):
    """
    Append new row in the items list
    """
    global items_list
    
    if not key in item_value_widget_funcs:
        # cannot handle unknown metadata item type, so don't display it at all
        return

    if key in titles_map:
        label_text = titles_map[key]
    else:
        label_text = key
    
    row = items_table.get_property("n-rows")
    
    label = gtk.Label(label_text)
    label.set_alignment(0, 0.5)
    items_table.attach(label, 0, 1, row, row+1, xoptions=gtk.FILL)
    
    create_item_value_widget_func, process_item_value_widget_func = item_value_widget_funcs[key]
    item_value_widget, text_entry_widget =  create_item_value_widget_func(initial_value=initial_value, **kwargs)
    items_table.attach(item_value_widget, 1, 2, row, row+1)

    fill_button = gtk.Button()
    fill_button.set_tooltip_text(_("Fill the field using values from the database"))
    fill_button.set_focus_on_click(False)
    img = gtk.Image()
    img.set_from_stock(gtk.STOCK_SPELL_CHECK, gtk.ICON_SIZE_MENU)
    fill_button.add(img)
    items_table.attach(fill_button, 2, 3, row, row+1, xoptions=gtk.FILL)
    
    del_button = gtk.Button()
    del_button.set_tooltip_text(_("Delete this item"))
    del_button.set_focus_on_click(False)
    img = gtk.Image()
    img.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_MENU)
    del_button.add(img)
    items_table.attach(del_button, 3, 4, row, row+1, xoptions=gtk.FILL)

    item_id = str(len(items_list))
    items_list[item_id] = (key, label, item_value_widget, fill_button)
    item_value_widget.set_data("item_id", item_id)
    fill_button.connect("clicked", on_edit_item, dialog, text_entry_widget, key)
    del_button.connect("clicked", on_delete_item, items_table, item_id)
    
    # set focus to just created item
    text_entry_widget.grab_focus()
    
def process_item(key, item_value_widget):
    """
    Extract data from widget and form metadata item
    
    @return: metadata item
    """
    if not key in item_value_widget_funcs:
        # cannot handle unknown metadata item type, so don't process it at all
        return
    
    create_item_value_widget_func, process_item_value_widget_func = item_value_widget_funcs[key]
    return process_item_value_widget_func(item_value_widget)
    
def on_resize(dialog, event, settings_prefix):
    settings.remember_window_geometry(dialog, settings_prefix)

def on_delete_item(button, items_table, item_id):
    """
    Delete metadata item from the list
    """
    global items_list
    
    key, label, item_value_widget, fill_button = items_list[item_id]
    
    label.destroy()
    item_value_widget.destroy()
    fill_button.destroy()
    button.destroy()
    del items_list[item_id]
    
    
def on_edit_item_treeview_click(treeview, entry):
    model, iter = treeview.get_selection().get_selected()
    if iter is None:
        return
    
    entry.set_text(model.get_value(iter, 0))
    
def on_edit_item_treeview_activated(treeview, path, view_column, edit_item_dialog):
    edit_item_dialog.response(1)

def on_edit_item_entry_activated(entry, treeview):
    """
    User pressed <Enter> inside text entry
    """
    # perform search and display matches in the treeview
    attr_name = entry.get_data("attr_name")
    if attr_name is None:
        return
    
    treestore = treeview.get_model()
    if treestore is None:
        return 
    
    results = collection.get_metadata_item_matches(attr_name, su.ustr2unicode(entry.get_text()))
    treestore.clear()
    [treestore.append(None, (x,)) for x in results]

def on_edit_item(button, dialog, text_entry_widget, attr_name):
    """ 
    Display dialog box for editing selected item value
    """
    edit_item_dialog = dialog.get_data("edit_item_dialog")
    treeview = edit_item_dialog.get_data("treeview")
    entry = edit_item_dialog.get_data("entry")
    
    edit_item_dialog.set_transient_for(dialog)
    entry.set_text(text_entry_widget.get_text())
    entry.set_data("attr_name", attr_name)
    entry.grab_focus()
    
    treestore = gtk.TreeStore(gobject.TYPE_STRING)
    treeview.set_model(treestore)
    
    # execute single item value edit dialog
    result = edit_item_dialog.run()
    
    treeview.set_model(None)
    entry.set_data("attr_name", None)
    edit_item_dialog.set_transient_for(None)
    edit_item_dialog.hide()
    
    if result == 1:
        text_entry_widget.set_text(entry.get_text())
    

def on_new_item_menu(button, dialog, new_item_menu):
    r = button.get_allocation()
    x, y = button.translate_coordinates(dialog, 0, r.height)
    wx, wy = dialog.window.get_origin()

    new_item_menu.popup(None, None, 
        lambda menu: (x+wx, y+wy, True),
        0,
        gtk.get_current_event_time())
    
def on_new_item(menuitem, items_table, key, dialog):
    append_item(dialog, items_table, key, "")
    items_table.show_all()
